<template>
  <div>
    <div>
      <button @click="generateXML">生成xml文件</button>
      <button @click="addHumanTask">添加普通节点</button>
      <button @click="addEntryCriteria">添加准入条件</button>
      <button @click="addDocumentation" style="margin-left: 15px">
        添加文档节点
      </button>
      <button @click="addControl">添加控制元素</button>
      <button @click="addExtension">添加扩展元素</button>
      <button @click="replaceTask" style="margin-left: 15px">替换节点</button>
      <button @click="changeTask">更改节点形状</button>
      <button @click="delTask">删除节点</button>
      <button @click="addConnection" style="margin-left: 15px">
        准入条件的使用
      </button>
      <button @click="moveNode">移动节点</button>
      <button @click="hideNode">隐藏节点</button>
    </div>
    <div class="content" ref="container">
      <!-- cmmn-js会在这里渲染内容 -->
    </div>
  </div>
</template>

<script setup>
import CmmnModeler from "cmmn-js/lib/Modeler";
import { onMounted, ref, onUnmounted } from "vue";
import createDiagram from "./resources/newDiagram";
import flowableModdleDescriptor from "./resources/flowable.json";

// 定义一个简单的CMMN XML
const diagramXML = createDiagram();

const container = ref(null);
// 建模器和使用到的模块
let cmmnModeler,
  elementFactory,
  modeling,
  elementRegistry,
  selection,
  cmmnFactory,
  moddle,
  canvas,
  eventBus,
  graphicsFactory;

onMounted(() => {
  // 创建建模器
  cmmnModeler = new CmmnModeler({
    container: container.value,
    moddleExtensions: {
      flowable: flowableModdleDescriptor,
    },
  });

  cmmnModeler.importXML(diagramXML, function (err) {
    if (err) {
      console.error("导入出错", err);
      return;
    }
    console.log("导入成功");
  });
  elementFactory = cmmnModeler.get("elementFactory");
  modeling = cmmnModeler.get("modeling");
  elementRegistry = cmmnModeler.get("elementRegistry");
  selection = cmmnModeler.get("selection");
  cmmnFactory = cmmnModeler.get("cmmnFactory");
  moddle = cmmnModeler.get("moddle");
  canvas = cmmnModeler.get("canvas");
  eventBus = cmmnModeler.get("eventBus");
  graphicsFactory = cmmnModeler.get("graphicsFactory");
});

onUnmounted(() => {
  // 销毁建模器
  if (cmmnModeler) {
    cmmnModeler.destroy();
  }
});

// 生成xml文件
const generateXML = () => {
  cmmnModeler.saveXML({ format: true }, (err, xml) => {
    console.log(xml);
  });
};

// 添加普通节点
const addHumanTask = () => {
  const target = elementRegistry.get("CasePlanModel_1");
  // 方法一
  const ele = elementFactory.createPlanItemShape("cmmn:HumanTask");
  // 方法二，第一个参数为shape/connetion，代表节点/连线
  // const ele = elementFactory.create("shape", {
  //   type: "cmmn:PlanItem", // 容器类型
  //   definitionType: "cmmn:HumanTask", // 定义类型
  //   hidden: true, // 节点创建后隐藏，画布上不显示，此属性不会保存到xml文件中
  // });
  // 参数 ele要添加的节点、位置、target要添加节点的双亲节点
  modeling.createShape(ele, { x: 500, y: 300 }, target);
};

// 添加准入条件
const addEntryCriteria = () => {
  const target = selection.get()?.[0];
  if (!target) return;
  // 方法一
  const entryEle = elementFactory.createCriterionShape("cmmn:EntryCriterion");
  // 方法二 准入条件不存在definitionRef，不需要指定definitionType
  // const entryEle = elementFactory.create('shape', {
  //   type: 'cmmn:EntryCriterion',
  // });
  // target指需要添加条件的目标节点
  modeling.createShape(
    entryEle,
    { x: target.x, y: target.y + target.height / 2 },
    target,
    {
      attach: true,
    }
  );
  // 创建完成后指定条件节点的目标
  // modeling.updateAttachment(entryEle, target);
};

// 添加documentation文档节点
const addDocumentation = () => {
  const shape = selection.get()?.[0];
  if (!shape) return;
  // 通过text指定内容
  const exp = cmmnFactory.create("cmmn:Documentation", {
    text: "111",
  });

  let documentation = shape.businessObject.definitionRef.documentation || [];
  documentation.push(exp);
  modeling.updateControls(shape, {
    documentation,
  });
};

// 添加控制元素
const addControl = () => {
  // 手动开始
  // const shape = selection.get()?.[0];
  // if (!shape) return;
  // const exp = cmmnFactory.create("cmmn:ManualActivationRule");
  // modeling.updateControls(shape, {
  //   manualActivationRule: exp,
  // });

  // 指定子案例节点内的子案例id
  // const target = elementRegistry.get("CasePlanModel_1");
  // const ele = elementFactory.createPlanItemShape("cmmn:CaseTask");
  // modeling.createShape(ele, { x: 650, y: 300 }, target);
  // // 通过body指定表达式内容
  // const exp = cmmnModeler.get("cmmnFactory").create("cmmn:Expression", {
  //   body: `<![CDATA[CasePlanModel_2]]>`,
  // });
  // modeling.updateControls(ele, {
  //   caseRefExpression: exp,
  // });

  // 指定时间监听器中的时间表达式
  const target = elementRegistry.get("CasePlanModel_1");
  const ele = elementFactory.createPlanItemShape("cmmn:TimerEventListener");
  modeling.createShape(ele, { x: 650, y: 300 }, target);
  const exp = cmmnModeler.get("cmmnFactory").create("cmmn:Expression", {
    body: `<![CDATA[${new Date().toISOString()}]]>`,
  });
  modeling.updateControls(ele, {
    timerExpression: exp,
  });
};

// 添加扩展元素
const addExtension = () => {
  const shape = selection.get()?.[0];
  if (!shape) return;
  // 获取原有的扩展元素
  let extensionElements =
    shape.businessObject?.definitionRef?.extensionElements;
  // 创建扩展元素并配置描述文件中定义好的属性
  const exp = moddle.create("flowable:TaskListener", {
    event: "create",
    delegateExpression: "${nodeService}",
  });
  // 原有的扩展元素存在则添加 不存在则创建
  if (extensionElements) {
    extensionElements.get("values").push(exp);
  } else {
    extensionElements = moddle.create("cmmn:ExtensionElements", { values: [] });
    // 通过values属性添加扩展元素
    extensionElements.values.push(exp);
  }
  // 更新
  modeling.updateControls(shape, {
    extensionElements: extensionElements,
  });
};

// 用新节点替换选中节点
const replaceTask = () => {
  const oldShape = selection.get()?.[0];
  if (!oldShape) return;
  let newShape = elementFactory.createPlanItemShape("cmmn:DecisionTask");
  // 指定newShape的位置，不指定会因为找不到节点位置而报错
  newShape.x = oldShape.x + newShape.width / 2;
  newShape.y = oldShape.y + newShape.height / 2;
  // 传递oldShape中需要保留的值，注意不要替换掉newShape.businessObject.definitionRef定义本身
  newShape.businessObject.definitionRef.name =
    oldShape.businessObject?.definitionRef?.name;
  modeling.replaceShape(oldShape, newShape);
};

// 更改节点形状
const changeTask = () => {
  const shape = selection.get()?.[0];
  if (!shape) return;
  // const gfx = elementRegistry.getGraphics(shape);
  // gfx.style.fill = "blue";
  // gfx.style.stroke = "red";
  // 添加类名
  canvas.addMarker(shape.id, "highlight");
};

// 删除节点
const delTask = () => {
  const chosenTask = selection.get()?.[0];
  if (!chosenTask) return;
  modeling.removeElements([chosenTask]);
};

// 准入条件的使用
const addConnection = () => {
  // 创建节点和准入条件
  const target = elementRegistry.get("CasePlanModel_1");
  const task1 = modeling.createShape(
    elementFactory.createPlanItemShape("cmmn:Task"),
    { x: 400, y: 200 },
    target
  );
  const task2 = modeling.createShape(
    elementFactory.createPlanItemShape("cmmn:Task"),
    { x: 400, y: 300 },
    target
  );
  const task3 = modeling.createShape(
    elementFactory.createPlanItemShape("cmmn:Task"),
    { x: 600, y: 250 },
    target
  );
  const task3Entry = elementFactory.createCriterionShape("cmmn:EntryCriterion");
  modeling.createShape(
    task3Entry,
    { x: task3.x, y: task3.y + task3.height / 2 },
    task3,
    {
      attach: true,
    }
  );

  // 通过modeling.connect创建元素之间的连线，参数3指定连线类型
  // standardEvent会自动设置，与节点的连线默认complete，与监听器的连线默认occur
  // 可通过connection.businessObject.cmmnElementRef.standardEvent更改
  const connection = modeling.connect(task1, task3Entry, {
    type: "cmmn:PlanItemOnPart",
  });
  // 设置连线的isStandardEventVisible属性，可以控制连线上方的complete状态字样是否显示
  modeling.updateProperties(connection, {
    isStandardEventVisible: false,
  });
  // 连线的自定义渲染可以通过自定义renderer中的drawConnection方法实现，和节点的自定义渲染类似

  // 更改连线
  // 通过modeling.reconnectStart、modeling.reconnectEnd可重新指定连线的新起点和新终点
  // 参数一是连线，参数二是新起点/终点
  // 参数三是连线在画布上的点的位置，{x,y}的对象数组，可参考connection原来的waypoints重新设置
  let newWaypoints = [...connection.waypoints];
  let newY = task2.y + task2.height / 2;
  newWaypoints[0] = { x: newWaypoints[0].x, y: newY };
  newWaypoints[1] = { x: newWaypoints[1].x, y: newY };
  modeling.reconnectStart(connection, task2, newWaypoints);
};

// 移动节点
const moveNode = () => {
  const shape = selection.get()?.[0];
  if (!shape) return;
  // 参数中的xy指与原位置的偏移
  // 移动单个元素，参数为目标元素、位置偏移、新位置的双亲元素，其附属的条件元素不会跟随移动
  // modeling.moveShape(shape, { x: 20, y: 20 }, shape.parent);

  // 移动多个元素位置，参数为元素数组、位置偏移，在元素存在附属时，可用此方法同步移动
  modeling.moveElements([shape, ...shape.attachers], { x: 20, y: 20 });
};

// 隐藏节点
const hideNode = () => {
  // 元素与连线的创建命令事件，3000为优先级，默认优先级1000，监听后再创建元素会被隐藏
  // eventBus.on("commandStack.shape.create.postExecute", 3000, function (e) {
  //   e.context.shape.hidden = true;
  // });
  // eventBus.on("commandStack.connection.create.postExecute", 3000, function (e) {
  //   e.context.connection.hidden = true;
  // });
  // 获取所有元素
  const allElements = elementRegistry.getAll();
  allElements.forEach((element) => {
    if (element.id === "CasePlanModel_1") return;
    element.hidden = true;
    // 更新元素表示
    const gfx = elementRegistry.getGraphics(element, false);
    // 参数，shape/connetion、目标节点、对应表示
    graphicsFactory.update("shape", element, gfx);
  });
};
</script>

<style>
@import "cmmn-js/dist/assets/diagram-js.css";
@import "cmmn-js/dist/assets/cmmn-font/css/cmmn.css";
@import "cmmn-js/dist/assets/cmmn-font/css/cmmn-embedded.css";

.content {
  position: relative;
  width: 100%;
  height: 100%;
}

/* 确保所有元素可点击 */
.djs-container {
  font-family: "Arial", sans-serif;
}

/* 调色板样式 */
.djs-palette {
  position: absolute;
  left: 20px;
  top: 20px;
}
</style>
